/*
 * 描述： <描述>
 * 修改人： rain
 * 修改时间： 2015年12月14日
 * 项目： hoe
 */
package com.rainhy.webcore.holder;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.rainhy.core.exception.RainhyException;
import com.rainhy.core.exception.RainhyNotLoginException;
import com.rainhy.core.util.AssertUtils;
import com.rainhy.webcore.event.RainhyLoginEvent;
import com.rainhy.webcore.event.RainhyLogoutEvent;

/**
 * 
 * WebContext Holder
 * 
 * @author rain
 * @version [版本号, 2015年12月15日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Component("rainhy.webcore.WebContextHolder")
public class WebContextHolder implements InitializingBean, FactoryBean<WebContextHolder> {
    
    /** 日志 */
    private static final Logger logger = LoggerFactory.getLogger(WebContextHolder.class);
    
    /** 当前登录人员在 session 中的 key */
    private static final String SESSION_LOGIN_KEY = "rainhy.webcore.WebContextHolder.Login.Key";
    
    /** 对自身的唯一引用 */
    private static WebContextHolder holder;
    
    /**
     * 
     * 添加 Cookie
     *
     * @param cookie
     * 
     * @return void [返回类型说明]
     * @throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年10月19日]
     * @author rain
     */
    public static void addCookie(Cookie cookie) {
        getResponse().addCookie(cookie);
    }
    
    /**
     * 
     * 添加 Cookie
     *
     * @param name cookie名
     * @param value cookie值
     * 
     * @return void [返回类型说明]
     * @throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年10月19日]
     * @author rain
     */
    public static void addCookie(String name, String value) {
        getResponse().addCookie(new Cookie(name, value));
    }
    
    /**
     * 
     * 绑定当前 rquest,response 请求到当前线程
     *
     * @param request
     * @param response
     * 
     * @return void [返回类型说明]
     * @throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年10月5日]
     * @author rain
     */
    public static void bindCurrentRequestToThread(HttpServletRequest request, HttpServletResponse response) {
        get().install(request, response);
    }
    
    /** 返回自身唯一引用 */
    public static WebContextHolder get() {
        return WebContextHolder.holder;
    }
    
    /**
     * 
     * 获取 Cookie
     *
     * @param cookieName Cookie名
     * 
     * @return Cookie
     * @throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年10月19日]
     * @author rain
     */
    public static Cookie getCookie(String cookieName) {
        Cookie[] cookies = getRequest().getCookies();
        if (ArrayUtils.isNotEmpty(cookies)) {
            for (Cookie cookie : cookies) {
                if (StringUtils.equals(cookie.getName(), cookieName)) {
                    return cookie;
                }
            }
        }
        return null;
    }
    
    /**
     * 
     * 获取 Cookie 值
     *
     * @param cookieName Cookie名
     * 
     * @return String Cookie值
     * @throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年10月19日]
     * @author rain
     */
    public static String getCookieValue(String cookieName) {
        return getCookie(cookieName).getValue();
    }
    
    /**
     * 
     * 获取请求的客户端ip地址,如果是本地地址一般返回"localhost"
     *
     * @return String ip 地址
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年1月20日]
     * @author rain
     */
    public static String getRemoteHost() {
        HttpServletRequest request = getRequest();
        String ip = request.getHeader("x-forwarded-for");
        if ((StringUtils.isBlank(ip)) || ("unknown".equalsIgnoreCase(ip))) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if ((StringUtils.isBlank(ip)) || ("unknown".equalsIgnoreCase(ip))) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if ((StringUtils.isBlank(ip)) || ("unknown".equalsIgnoreCase(ip))) {
            ip = request.getRemoteAddr();
        }
        ip = ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
        ip = ip.equals("127.0.0.1") ? "localhost" : ip;
        
        return ip;
    }
    
    /**
     * 
     * 获取HttpServletRequest
     *
     * @return HttpServletRequest [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年1月20日]
     * @author rain
     */
    public static HttpServletRequest getRequest() {
        HttpServletRequest request = get().getCurrentRequest();
        if (request != null) {
            return request;
        }
        
        RequestAttributes requestAttributes = null;
        try {
            requestAttributes = RequestContextHolder.currentRequestAttributes();
        } catch (IllegalStateException e) {
        }
        
        if (requestAttributes != null && requestAttributes instanceof ServletRequestAttributes) {
            request = ((ServletRequestAttributes) requestAttributes).getRequest();
        }
        
        AssertUtils.notNull(request, "获取当前线程绑定的HttpServletResponse失败,请在适当的地方(例如springmvc拦截器)调用intall()来初始化 request 和 response.");
        return request;
    }
    
    /**
     * 
     * 获取 request 属性
     *
     * @param name 属性名
     * 
     * @return Object [返回类型说明]
     * @exception [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月14日]
     * @author rain
     */
    @SuppressWarnings("unchecked")
    public static <T> T getRequestAttribute(String name, Class<T> clazz) {
        return (T) getRequest().getAttribute(name);
    }
    
    /**
     * 
     * 获取 request 参数
     *
     * @param name 参数名
     * 
     * @return String [返回类型说明]
     * @exception [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月14日]
     * @author rain
     */
    public static String getRequestParameter(String name) {
        return getRequest().getParameter(name);
    }
    
    /**
     * 
     * 获取HttpServletResponse
     *
     * @return HttpServletRequest [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年1月20日]
     * @author rain
     */
    public static HttpServletResponse getResponse() {
        HttpServletResponse response = get().getCurrentResponse();
        AssertUtils.notNull(response, "获取当前线程绑定的HttpServletResponse失败,请在适当的地方(例如springmvc拦截器)调用intall()来初始化 request 和 response.");
        return response;
    }
    
    /**
     * 
     * 获取项目路径<br>
     *
     * @return String [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年1月20日]
     * @author rain
     */
    public static String getServletContextPath() {
        return getRequest().getContextPath();
    }
    
    /**
     * 
     * 登录<br>
     * 登录时调用,传入一个 key 缓存起来,一般传入操作员 id<br>
     * 如果已经登录,则覆盖
     *
     * @param loginkey 登录Key
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年1月20日]
     * @author rain
     */
    public static void login(String loginkey) {
        AssertUtils.notNull(loginkey, "loginkey is null!", new Object[0]);
        
        HttpSession session = HttpSessionHolder.getSession(true);
        session.setAttribute(SESSION_LOGIN_KEY, loginkey);
        
        SpringContextHolder.publishEvent(new RainhyLoginEvent(loginkey));
    }
    
    /**
     * 
     * 校验是否登陆<br>
     * 如果没有登陆,则抛出 RainhyNotLoginException 异常
     * 
     * @return void [返回类型说明]
     * @exception RainhyNotLoginException 未登录异常
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年1月20日]
     * @author rain
     */
    public static void loginCheck() {
        if (!logined()) {
            throw new RainhyNotLoginException();
        }
    }
    
    /**
     * 
     * 是否已经登陆
     *
     * @return boolean [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年1月20日]
     * @author rain
     */
    public static boolean logined() {
        HttpSession session = HttpSessionHolder.getSession(false);
        if (session == null) {
            return false;
        }
        return session.getAttribute(SESSION_LOGIN_KEY) != null;
    }
    
    /**
     * 
     * 获取登陆 Key<br>
     * 一般保存着操作员 id
     *
     * @return String 登陆 Key
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年1月20日]
     * @author rain
     */
    public static String loginKey() {
        HttpSession session = HttpSessionHolder.getSession(false);
        if (session == null) {
            throw new RainhyNotLoginException();
        }
        String loginKey = (String) session.getAttribute(SESSION_LOGIN_KEY);
        if (StringUtils.isBlank(loginKey)) {
            throw new RainhyNotLoginException();
        }
        return loginKey;
    }
    
    /**
     * 
     * 退出<br>
     * 会清空对应的登陆 Key
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年1月20日]
     * @author rain
     */
    public static void logout() {
        HttpSession session = HttpSessionHolder.getSession(false);
        if (session != null) {
            String loginKey = (String) session.getAttribute(SESSION_LOGIN_KEY);
            if (StringUtils.isNotBlank(loginKey)) {
                SpringContextHolder.publishEvent(new RainhyLogoutEvent(loginKey));
                session.removeAttribute(SESSION_LOGIN_KEY);
                try {
                    session.invalidate();
                } catch (IllegalStateException localIllegalStateException) {
                } catch (Exception e) {
                    throw new RainhyException(e);
                }
                
            }
        }
    }
    
    /**
     * 
     * 重定位 controllerPath
     *
     * @param controllerPath
     * 
     * @return String 新的地址
     * @throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月29日]
     * @author rain
     */
    public static String redirect(String controllerPath) {
        if (!StringUtils.startsWith(controllerPath, "/")) {
            controllerPath = "/".concat(controllerPath);
        }
        return "redirect:".concat(controllerPath);
    }
    
    /**
     * 
     * 把 request 从当前线程解除绑定
     * 
     * @return void [返回类型说明]
     * @throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年10月5日]
     * @author rain
     */
    public static void removeCurrentRequestFromThread() {
        get().uninstall();
    }
    
    /**
     * 
     * 设置 request 属性
     *
     * @param name 属性名
     * @param value 值
     * 
     * @return void [返回类型说明]
     * @exception [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月14日]
     * @author rain
     */
    public static void setRequestAttribute(String name, Object value) {
        getRequest().setAttribute(name, value);
    }
    
    /**
     * 
     * 包装地址
     *
     * @param url 在 url 前面自动加上项目头
     * 
     * @return String 跳转地址
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年1月20日]
     * @author rain
     */
    public static String wrapUrl(String url) {
        if (!StringUtils.startsWith(url, "/")) {
            url = "/".concat(url);
        }
        return getServletContextPath().concat(url);
    }
    
    /** HttpServletRequest */
    private ThreadLocal<HttpServletRequest> currentRequest = new ThreadLocal<>();
    
    /** HttpServletResponse */
    private ThreadLocal<HttpServletResponse> currentResponse = new ThreadLocal<>();
    
    @Override
    public void afterPropertiesSet() throws Exception {
        WebContextHolder.holder = this;
        logger.info("加载[WebContext Holder]...");
    }
    
    /** 当前访问线程的HttpServletRequest */
    public HttpServletRequest getCurrentRequest() {
        return currentRequest.get();
    }
    
    /** 当前访问线程的HttpServletResponse */
    public HttpServletResponse getCurrentResponse() {
        HttpServletResponse response = currentResponse.get();
        return response;
    }
    
    @Override
    public WebContextHolder getObject() throws Exception {
        return WebContextHolder.get();
    }
    
    @Override
    public Class<?> getObjectType() {
        return WebContextHolder.class;
    }
    
    /**
     * 
     * 初始化 WebContextHolder
     *
     * @param request
     * @param response
     * 
     * @return void [返回类型说明]
     * @throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年10月5日]
     * @author rain
     */
    public void install(HttpServletRequest request, HttpServletResponse response) {
        this.currentRequest.set(request);
        this.currentResponse.set(response);
    }
    
    @Override
    public boolean isSingleton() {
        return true;
    }
    
    /**
     * 
     * 销毁容器中的数据，防止由于线程池造成的线程复用后，引用到不该引用的内容
     * 
     * @return void [返回类型说明]
     * @throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月29日]
     * @author rain
     */
    public void uninstall() {
        this.currentRequest.remove();
        this.currentResponse.remove();
    }
}
